/**
 *  Copyright 2013 Nate Dierk
 *
 *  COMP 590 -- Fall 2013
 */

#ifndef GPixel_DEFINED
#define GPixel_DEFINED

#include "GTypes.h"
#include "GColor.h"

#define GPIXEL_SCALE_FACTOR 255

#define GPIXEL_SHIFT_A  24
#define GPIXEL_SHIFT_R  16
#define GPIXEL_SHIFT_G   8
#define GPIXEL_SHIFT_B   0


/*  Defines our 32bit pixel to be just an int. It stores its components based
 *  on the defines GPIXEL_SHIFT_... for each component.
 */
typedef uint32_t GPixel;

enum BlendType{
  SET,       // ignores and overwrites any existing values: dst = src
  SRC_OVER,  // source over blending: dst = src + (1 - src.fA)*dst
};


///////////////////////////////////////////////////////////////////////////////
// Getters for single components

static inline int GPixel_GetA(GPixel p) { return (p >> GPIXEL_SHIFT_A) & 0xFF; }
static inline int GPixel_GetR(GPixel p) { return (p >> GPIXEL_SHIFT_R) & 0xFF; }
static inline int GPixel_GetG(GPixel p) { return (p >> GPIXEL_SHIFT_G) & 0xFF; }
static inline int GPixel_GetB(GPixel p) { return (p >> GPIXEL_SHIFT_B) & 0xFF; }


///////////////////////////////////////////////////////////////////////////////
// Forward Declartions of other (public) methods (easy viewing of signatures)

static GPixel GPixel_PackARGB(unsigned a, unsigned r, unsigned g, unsigned b);
static GPixel GPixel_PackColor(const GColor& color);

static void GPixel_Scale(const GPixel& pix, unsigned factor);
static void GPixel_Blend(GPixel* dst, const GPixel& src, const BlendType blend_t);





//for debugging purposes
static void GPixel_Print(const GPixel& p, char* name){
  float a, r, g, b;
  a = (float) GPixel_GetA(p) / GPIXEL_SCALE_FACTOR;
  r = (float) GPixel_GetR(p) / GPIXEL_SCALE_FACTOR;
  g = (float) GPixel_GetG(p) / GPIXEL_SCALE_FACTOR;
  b = (float) GPixel_GetB(p) / GPIXEL_SCALE_FACTOR;

  printf("Pixel '%s': {%f, %f, %f, %f}\n", name, a, r, g, b);
}


///////////////////////////////////////////////////////////////////////////////
// Method Implementations

/*  Asserts that a, r, g, b are already in premultiply form, and simply
 *  packs them into a GPixel.
 */
static inline GPixel GPixel_PackARGB(unsigned a, unsigned r,
                                     unsigned g, unsigned b) {
    GASSERT(a <= 255);
    GASSERT(r <= a);
    GASSERT(g <= a);
    GASSERT(b <= a);
    
    return  (a << GPIXEL_SHIFT_A) |
            (r << GPIXEL_SHIFT_R) |
            (g << GPIXEL_SHIFT_G) |
            (b << GPIXEL_SHIFT_B);
}

static inline unsigned GPixel_CompPreMul(float alpha, float c){
  return (unsigned) (alpha * c * GPIXEL_SCALE_FACTOR);
}

// premul and pack into pixel
static inline GPixel GPixel_PackColor(const GColor& color){
  return GPixel_PackARGB( 
    GPixel_CompPreMul(    1.0f, color.fA), //alpha is special, doesn't pre-mul itself
    GPixel_CompPreMul(color.fA, color.fR), 
    GPixel_CompPreMul(color.fA, color.fG), 
    GPixel_CompPreMul(color.fA, color.fB)
    ); 
}

static inline void GPixel_Scale(GPixel* pix, unsigned factor){
  GASSERT(factor <= 255);

  uint32_t a, r, g, b;
  a = ((GPixel_GetA(*pix) * factor) + 255) >> 8;
  r = ((GPixel_GetR(*pix) * factor) + 255) >> 8;
  g = ((GPixel_GetG(*pix) * factor) + 255) >> 8;
  b = ((GPixel_GetB(*pix) * factor) + 255) >> 8;

  *pix = GPixel_PackARGB(a, r, g, b);
}

static inline void GPixel_Blend(GPixel* dst, const GPixel& src, const BlendType blend_t){
  unsigned sA = GPixel_GetA(src);

  if (GPIXEL_SCALE_FACTOR == sA
    || SET == blend_t) {
    *dst = src;
  } else if (SRC_OVER == blend_t){
    GPixel_Scale(dst, (GPIXEL_SCALE_FACTOR - sA));
    *dst = src + *dst;
  }
};

#endif